package com.opencee.cloud.bpm.service;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Maps;
import com.opencee.cloud.bpm.vo.BpmUserTaskInfoVO;
import de.odysseus.el.ExpressionFactoryImpl;
import de.odysseus.el.util.SimpleContext;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.*;
import org.activiti.engine.*;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.history.HistoricProcessInstanceQuery;
import org.activiti.engine.history.HistoricTaskInstance;
import org.activiti.engine.impl.persistence.entity.TaskEntity;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ExecutionQuery;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.runtime.ProcessInstanceQuery;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 流程引擎通用类
 *
 * @author: liuyadu
 * @date: 2019/4/4 10:53
 * @description:
 */
@Service
public class ProcessEngineService {
    @Autowired
    protected RepositoryService repositoryService;
    @Autowired
    protected RuntimeService runtimeService;
    @Autowired
    protected TaskService taskService;
    @Autowired
    protected FormService formService;
    @Autowired
    protected HistoryService historyService;
    @Autowired
    protected IdentityService identityService;

    /**
     * 启动流程并返回流程实例
     *
     * @param processDefinitionId
     * @param businessKey
     * @param variables
     * @return
     * @throws Exception
     */
    public ProcessInstance startupProcessById(String processDefinitionId, String businessKey, String userId, Map<String, Object> variables) {
        if (variables == null) {
            variables = Maps.newHashMap();
        }
        identityService.setAuthenticatedUserId(userId);
        ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinitionId, businessKey, variables);
        return processInstance;
    }

    /**
     * 启动流程并返回流程实例
     *
     * @param processDefinitionKey
     * @param businessKey
     * @param variables
     * @return
     * @throws Exception
     */
    public ProcessInstance startupProcessByKey(String processDefinitionKey, String businessKey, String userId, Map<String, Object> variables, String tenantId) {
        if (variables == null) {
            variables = Maps.newHashMap();
        }
        ProcessInstance processInstance = null;
        identityService.setAuthenticatedUserId(userId);
        if (StringUtils.isNotEmpty(tenantId)) {
            processInstance = runtimeService.startProcessInstanceByKeyAndTenantId(processDefinitionKey, businessKey, variables, tenantId);
        } else {
            processInstance = runtimeService.startProcessInstanceByKey(processDefinitionKey, businessKey, variables);
        }
        return processInstance;
    }


    /**
     * 读取已结束的流程
     *
     * @param processDefinitionKey
     * @param startRow
     * @param endRow
     * @return
     */
    public List<HistoricProcessInstance> findFinishedProcessInstances(String processDefinitionKey, int startRow, int endRow) {
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery().finished().orderByProcessInstanceEndTime().desc();
        if (org.apache.commons.lang3.StringUtils.isNotEmpty(processDefinitionKey)) {
            query.processDefinitionKey(processDefinitionKey);
        }
        List<HistoricProcessInstance> list = query.listPage(startRow, endRow);
        return list;
    }

    /**
     * 读取运行中的流程
     *
     * @param processDefinitionKey
     * @param startRow
     * @param endRow
     * @return
     */
    public List<ProcessInstance> findRunningProcessInstance(String processDefinitionKey, int startRow, int endRow) {
        ProcessInstanceQuery query = runtimeService.createProcessInstanceQuery().active()
                .orderByProcessInstanceId().desc();
        if (org.apache.commons.lang3.StringUtils.isNotEmpty(processDefinitionKey)) {
            query.processDefinitionKey(processDefinitionKey);
        }
        List<ProcessInstance> list = query.listPage(startRow, endRow);
        return list;
    }

    /**
     * 渲染任务表单
     *
     * @param taskId
     * @return 返回渲染后内容
     */
    public String getRenderedTaskForm(String taskId) {
        return formService.getRenderedTaskForm(taskId).toString();
    }

    /**
     * 渲染流程启动表单
     *
     * @param processDefinitionId
     * @return
     */
    public String getRenderedStartForm(String processDefinitionId) throws Exception {
        return formService.getRenderedStartForm(processDefinitionId).toString();
    }


    /**
     * 根据流程实例ID查询对应的流程实例
     *
     * @param processInstanceId
     * @return
     * @throws Exception
     */
    public ProcessInstance findProcessInstanceById(String processInstanceId)
            throws Exception {
        // 找到流程实例
        ProcessInstance processInstance = runtimeService
                .createProcessInstanceQuery().processInstanceId(processInstanceId)
                .singleResult();
        if (processInstance == null) {
            throw new Exception("流程不存在或已结束!");
        }
        return processInstance;
    }

    /**
     * 根据任务ID查询对应的流程实例
     *
     * @param taskId 任务ID
     * @return
     * @throws Exception
     */
    public ProcessInstance findProcessInstanceByTaskId(String taskId)
            throws Exception {
        return findProcessInstanceById(findTaskById(taskId).getProcessInstanceId());
    }


    /**
     * 根据任务ID获得任务实例
     *
     * @param taskId 任务ID
     * @return
     * @throws Exception
     */
    private TaskEntity findTaskById(String taskId) throws Exception {
        TaskEntity task = (TaskEntity) taskService.createTaskQuery().taskId(
                taskId).includeTaskLocalVariables().singleResult();
        if (task == null) {
            throw new Exception("任务实例未找到!");
        }
        return task;
    }


    /**
     * 根据流程实例ID和任务key值查询所有同级任务集合
     *
     * @param processInstanceId
     * @param key
     * @return
     */
    private List<Task> findTaskListByKey(String processInstanceId, String key) {
        return taskService.createTaskQuery().processInstanceId(
                processInstanceId).includeTaskLocalVariables().taskDefinitionKey(key).list();
    }

    /**
     * 提交流程
     *
     * @param taskId    当前任务ID
     * @param variables 流程变量
     * @throws Exception
     */
    public void completeTask(String taskId, Map<String, Object> variables) {
        if (variables == null) {
            variables = new HashMap<String, Object>();
        }
        taskService.complete(taskId, variables);
    }

    /**
     * 将任务转移给其他人办理
     *
     * @param taskId   当前任务节点ID
     * @param userName 被转办人userName
     */
    public void transferAssignee(String taskId, String userName) {
        taskService.setAssignee(taskId, userName);
    }


    /**
     * 将任务委托给其他人办理
     *
     * @param taskId
     * @param userName
     */
    public void delegateTask(String taskId, String userName) {
        taskService.delegateTask(taskId, userName);
    }

    /**
     * 被委托人处理任务
     * 被委托人执行完毕后，任务工具又回到委托人名下，即A委托B处理，B处理完后，任务又回到A名下。
     *
     * @param taskId
     * @param variables
     */
    public void resolveTask(String taskId, Map<String, Object> variables) {
        if (variables == null) {
            variables = new HashMap<String, Object>();
        }
        taskService.resolveTask(taskId, variables);
    }

    /**
     * 查询代办列表
     *
     * @param userId
     * @return
     */
    public Page<Task> findTodoTask(String userId, int firstResult, int maxResults) {
        //得到用户待办
        TaskQuery query = taskService.createTaskQuery();
        query.includeTaskLocalVariables();
        if (StringUtils.isNotBlank(userId)) {
            query.taskAssignee(userId);
        }
        List<Task> list = query.listPage(firstResult, maxResults);
        Page page = new Page();
        page.setRecords(list);
        page.setTotal(query.count());
        return page;
    }

    /**
     * 查询流程定义列表
     *
     * @return
     */
    public Page<ProcessDefinition> findProcessDefinition(String key, int firstResult, int maxResults) {
        ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
        // 只查询最新版本
        query.latestVersion();
        if (StringUtils.isNotBlank(key)) {
            String processKey = "%" + key + "%";
            query.processDefinitionKeyLike(processKey);
        }
        List<ProcessDefinition> list = query.listPage(firstResult, maxResults);
        Page page = new Page();
        page.setRecords(list);
        page.setTotal(query.count());
        return page;
    }

    /**
     * 查询最新版本流程定义列表
     *
     * @return
     */
    public List<ProcessDefinition> findProcessDefinitionLastVersion() {
        List<ProcessDefinition> listAll = repositoryService
                .createProcessDefinitionQuery()
                .orderByProcessDefinitionVersion().asc()
                .list();
//定义有序的map，相同的key，假如添加map的值后者的值会覆盖前面相同key的值，map特点是保证key唯一
        Map<String, ProcessDefinition> map = new LinkedHashMap<String, ProcessDefinition>();
//遍历集合,根据key来覆盖前面的值，来保证最新的key覆盖老的key
        for (ProcessDefinition pd : listAll) {
            map.put(pd.getKey(), pd);
        }
        List<ProcessDefinition> pdList = new LinkedList<ProcessDefinition>(map.values());
        return pdList;
    }

    /**
     * 根据部署ID查询部署
     *
     * @param deploymentId
     * @return
     */
    public Deployment getDeploymentById(String deploymentId) {
        return repositoryService.createDeploymentQuery()
                // 根据部署ID查询
                .deploymentId(deploymentId)
                // 返回唯一结果
                .singleResult();
    }

    /**
     * 根据部署ID删除流程部署
     *
     * @param deploymentId
     */
    public void deleteDeployment(String deploymentId) {
        repositoryService.deleteDeployment(deploymentId);

    }


    /**
     * 激活流程
     *
     * @param processInstanceId
     */
    public void activateProcess(String processInstanceId) {
        runtimeService.activateProcessInstanceById(processInstanceId);
    }

    /**
     * 暂停流程
     *
     * @param processInstanceId
     */
    public void suspendProcess(String processInstanceId) {
        runtimeService.suspendProcessInstanceById(processInstanceId);
    }

    /**
     * 终止流程
     *
     * @param processInstanceId
     */
    public void stopProcess(String processInstanceId, String reason) {
        runtimeService.deleteProcessInstance(processInstanceId, reason);
    }


    /**
     * 获取当前任务节点的下一个任务节点
     *
     * @param task 当前任务节点
     * @return 下个任务节点
     * @throws Exception
     */
    public FlowElement getNextUserFlowElement(Task task) {
        // 取得已提交的任务
        HistoricTaskInstance historicTaskInstance = historyService.createHistoricTaskInstanceQuery()
                .taskId(task.getId()).singleResult();

        // 获得流程定义
        ProcessDefinition processDefinition = repositoryService.getProcessDefinition(historicTaskInstance.getProcessDefinitionId());

        //获得当前流程的活动ID
        ExecutionQuery executionQuery = runtimeService.createExecutionQuery();
        Execution execution = executionQuery.executionId(historicTaskInstance.getExecutionId()).singleResult();
        if (execution == null) {
            return null;
        }
        String activityId = execution.getActivityId();
        UserTask userTask = null;
        while (true) {
            //根据活动节点获取当前的组件信息
            FlowNode flowNode = getFlowNode(processDefinition.getId(), activityId);
            //获取该节点之后的流向
            List<SequenceFlow> sequenceFlowListOutGoing = flowNode.getOutgoingFlows();

            // 获取的下个节点不一定是userTask的任务节点，所以要判断是否是任务节点
            if (sequenceFlowListOutGoing.size() > 1) {
                // 如果有1条以上的出线，表示有分支，需要判断分支的条件才能知道走哪个分支
                // 遍历节点的出线得到下个activityId
                activityId = getNextActivityId(execution.getId(),
                        task.getProcessInstanceId(), sequenceFlowListOutGoing);
            } else if (sequenceFlowListOutGoing.size() == 1) {
                // 只有1条出线,直接取得下个节点
                SequenceFlow sequenceFlow = sequenceFlowListOutGoing.get(0);
                // 下个节点
                FlowElement flowElement = sequenceFlow.getTargetFlowElement();
                if (flowElement instanceof UserTask) {
                    // 下个节点为UserTask时
                    userTask = (UserTask) flowElement;
                    return userTask;
                } else if (flowElement instanceof ExclusiveGateway) {
                    // 下个节点为排它网关时
                    ExclusiveGateway exclusiveGateway = (ExclusiveGateway) flowElement;
                    List<SequenceFlow> outgoingFlows = exclusiveGateway.getOutgoingFlows();
                    // 遍历网关的出线得到下个activityId
                    activityId = getNextActivityId(execution.getId(), task.getProcessInstanceId(), outgoingFlows);
                }
            } else {
                // 没有出线，则表明是结束节点
                return null;
            }
        }
    }

    /**
     * 取得流程变量的值
     *
     * @param variableName      变量名
     * @param processInstanceId 流程实例Id
     * @return
     */
    public String getVariableValue(String variableName, String processInstanceId) {
        Execution execution = runtimeService
                .createExecutionQuery().processInstanceId(processInstanceId).list().get(0);
        Object object = runtimeService.getVariable(execution.getId(), variableName);
        return object == null ? "" : object.toString();
    }

    /**
     * 根据key和value判断el表达式是否通过
     *
     * @param key   el表达式key
     * @param el    el表达式
     * @param value el表达式传入值
     * @return
     */
    public boolean isCondition(String key, String el, String value) {
        ExpressionFactory factory = new ExpressionFactoryImpl();
        SimpleContext context = new SimpleContext();
        context.setVariable(key, factory.createValueExpression(value, String.class));
        ValueExpression e = factory.createValueExpression(context, el, boolean.class);
        return (Boolean) e.getValue(context);
    }

    /**
     * 根据el表达式取得满足条件的下一个activityId
     *
     * @param executionId
     * @param processInstanceId
     * @param outgoingFlows
     * @return
     */
    public String getNextActivityId(String executionId,
                                    String processInstanceId,
                                    List<SequenceFlow> outgoingFlows) {
        String activityId = null;
        // 遍历出线
        for (SequenceFlow outgoingFlow : outgoingFlows) {
            // 取得线上的条件
            String conditionExpression = outgoingFlow.getConditionExpression();
            // 取得所有变量
            Map<String, Object> variables = runtimeService.getVariables(executionId);
            String variableName = "";
            // 判断网关条件里是否包含变量名
            for (String s : variables.keySet()) {
                if (conditionExpression.contains(s)) {
                    // 找到网关条件里的变量名
                    variableName = s;
                }
            }
            String conditionVal = getVariableValue(variableName, processInstanceId);
            // 判断el表达式是否成立
            if (isCondition(variableName, conditionExpression, conditionVal)) {
                // 取得目标节点
                FlowElement targetFlowElement = outgoingFlow.getTargetFlowElement();
                activityId = targetFlowElement.getId();
                continue;
            }
        }
        return activityId;
    }

    /**
     * 根据活动节点和流程定义ID获取该活动节点的组件信息
     */
    public FlowNode getFlowNode(String processDefinitionId, String flowElementId) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
        FlowNode flowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(flowElementId);
        return flowNode;
    }

    /**
     * 只获取人工任务节点
     *
     * @return
     */
    public List<BpmUserTaskInfoVO> getUserTaskList(String processKey) {
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(processKey).latestVersion().singleResult();
        if (processDefinition == null) {
            return Collections.emptyList();
        }
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        List<BpmUserTaskInfoVO> userTaskList = new ArrayList<>();
        AtomicInteger atomicInteger = new AtomicInteger(0);
        Process process = bpmnModel.getMainProcess();
        //获取开始节点
        FlowNode flowNode = (FlowNode) process.getInitialFlowElement();
        // 获取连线后的下级所有节点
        findNextUseTaskList(userTaskList, flowNode, atomicInteger);
        // 去重
        List<BpmUserTaskInfoVO> list = userTaskList.stream().filter(distinctByKey(BpmUserTaskInfoVO::getTaskId)).collect(Collectors.toList());
        return list;
    }

    // 防止节点连线死循环,最大循环数。
    private static Integer MAX_LOOP = 200;

    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t -> seen.add(keyExtractor.apply(t));
    }

    public void findNextUseTaskList(List<BpmUserTaskInfoVO> userTaskList, FlowNode flowNode, AtomicInteger atomicInteger) {
        if (flowNode == null || atomicInteger.get() > MAX_LOOP) {
            return;
        }
        //获取当前节点输出连线
        List<SequenceFlow> outgoingFlows = flowNode.getOutgoingFlows();
        //遍历输出连线
        for (SequenceFlow outgoingFlow : outgoingFlows) {
            //获取输出节点元素
            FlowElement targetFlowElement = outgoingFlow.getTargetFlowElement();

            //用户任务接点
            if (targetFlowElement instanceof UserTask) {
                UserTask userTask = (UserTask) targetFlowElement;
                BpmUserTaskInfoVO vo = new BpmUserTaskInfoVO();
                vo.setTaskId(userTask.getId());
                vo.setTaskKey(userTask.getId());
                vo.setTaskName(userTask.getName());
                vo.setAssignee(userTask.getAssignee());
                vo.setCandidateUsers(userTask.getCandidateUsers());
                vo.setCandidateGroups(userTask.getCandidateGroups());
                // 当前节点为并行网关。后续输出节点均作为多人会签任务
                if (flowNode instanceof ParallelGateway) {
                    vo.setIsMulti(true);
                }
                // 多实例任务
                MultiInstanceLoopCharacteristics multi = userTask.getLoopCharacteristics();
                if (multi != null) {
                    // 并行-会签, 串行-依次
                    vo.setIsMulti(true);
                    // 是否为串行
                    vo.setIsSequential(multi.isSequential());
                    if (StringUtils.isNotBlank(multi.getInputDataItem())) {
                        if (StringUtils.startsWith(multi.getInputDataItem(), "${")) {

                        } else {
                            vo.setAssigneeUsers(Arrays.asList(StringUtils.split(multi.getInputDataItem(), ",")));
                        }
                    }
                }
                userTaskList.add(vo);
            }
            // 计数,防止节点连线死循环
            atomicInteger.incrementAndGet();
            findNextUseTaskList(userTaskList, (FlowNode) targetFlowElement, atomicInteger);
        }
    }


    public void getStartKey(){

       /* FlowElement initialFlowElement = process.getInitialFlowElement();
        if (initialFlowElement instanceof StartEvent) {
            StartEvent startEvent = (StartEvent) initialFlowElement;
            startEvent.setFormKey("aaaa");
            List<FormProperty> formProperties = startEvent.getFormProperties();
        }*/
    }

}
